﻿Imports System.Text.RegularExpressions
Imports System.IO

Public Class LookupArgHandler
    Implements IArgHandler

    Private Shared mFileCache As Dictionary(Of String, List(Of String))

    Shared Sub New()
        mFileCache = New Dictionary(Of String, List(Of String))
    End Sub

    Private mCommands As HashSet(Of String)
    Public Sub New()
        mCommands = New HashSet(Of String)
        mCommands.Add("object")
        mCommands.Add("token")
        mCommands.Add("file")
        mCommands.Add("material")
        mCommands.Add("tissue")
    End Sub

    Public Function GetValues(obj As RawObject, argument As ArgumentDefinition) As System.Collections.Generic.IEnumerable(Of String) Implements IArgHandler.GetValues
        Dim parts() As String = argument.TypeData.Split(":"c)
        If parts.Length = 2 AndAlso Not mCommands.Contains(parts(0)) Then
            Dim argData As String = "object:" & argument.TypeData
            parts = argData.Split(":"c)
        End If
        parts = ProcessReplacements(String.Join(":", parts), obj.Library, obj).Split(":"c)
        Dim command As String = parts(0)
        Select Case command
            Case "object"
                Dim objectType As ObjectType = objectType.ALL
                [Enum].TryParse(Of ObjectType)(parts(1), objectType)
                Dim subType As ObjectSubType = ObjectSubType.ALL
                [Enum].TryParse(Of ObjectSubType)(parts(2), subType)
                Dim ret As New List(Of String)(obj.Library.GetObjectIdsByType(objectType, subType))
                If parts.Length >= 4 Then
                    Dim extras As New List(Of String)(parts(3).Split(" "c))
                    ret.AddRange(extras)
                End If
                Return ret
            Case "token"
                Dim sourceObj As RawObject = obj.Library.GetObjectById(parts(1))
                If sourceObj IsNot Nothing Then
                    Dim ret As New List(Of String)
                    For Each token As String In sourceObj.GetTokenArguments(parts(2), True)
                        Dim tokenParts() As String = token.Split(":"c)
                        ret.Add(tokenParts(CInt(parts(3))))
                    Next
                    If parts.Length >= 5 Then
                        Dim extras As New List(Of String)(parts(4).Split(" "c))
                        ret.AddRange(extras)
                    End If
                    ret.Sort()
                    Return ret
                End If
            Case "file"
                If mFileCache.ContainsKey(parts(1)) Then
                    Return mFileCache(parts(1))
                Else
                    Dim ret As New List(Of String)
                    Dim reader As StreamReader = Nothing
                    Try
                        reader = New StreamReader(Path.Combine("Data", parts(1)))
                        Do While Not reader.EndOfStream
                            Dim line As String = reader.ReadLine
                            Dim fileParts() As String = line.Split(New Char() {" "c, CChar(vbTab)})
                            If fileParts.Length > 0 Then
                                ret.Add(fileParts(0))
                            End If
                        Loop
                    Catch ex As Exception
                    Finally
                        If reader IsNot Nothing Then
                            reader.Close()
                            reader.Dispose()
                        End If
                        mFileCache.Add(parts(1), ret)
                    End Try
                    Return ret
                End If
            Case "material"
                Dim sourceObj As RawObject = obj.Library.GetObjectById(parts(1))
                Return sourceObj.GetMaterials
            Case "tissue"
                Dim sourceObj As RawObject = obj.Library.GetObjectById(parts(1))
                Return sourceObj.GetTissues
        End Select

        Return New List(Of String)
    End Function

    Private Function ProcessReplacements(input As String, library As RawLibrary, obj As RawObject) As String
        'format "%TokenName-ArgumentIndex%
        Dim ret As String = input
        mObj = obj
        mLibrary = library
        ret = Regex.Replace(ret, "%(.*?)%", New MatchEvaluator(AddressOf EvaluateReplacement))
        ret = Regex.Replace(ret, "~self~", obj.objectId)
        Return ret
    End Function

    Private mObj As RawObject
    Private mLibrary As RawLibrary

    Private Function EvaluateReplacement(match As Match) As String
        Dim matchParts() As String = match.Value.Split("-"c)
        If matchParts.Count = 2 Then
            Dim args As String = mObj.GetFirstTokenArguments(matchParts(0))
            If args IsNot Nothing Then
                Dim argParts() As String = args.Split(":"c)
                Dim index As Integer = 0
                If Integer.TryParse(matchParts(1), index) AndAlso index < argParts.Count Then
                    Return argParts(index)
                End If
            End If
        End If
        Return ""
    End Function

    Public Function ValidateArgument(library As RawLibrary, obj As RawObject, argument As ArgumentDefinition, value As String) As String Implements IArgHandler.ValidateArgument
        Dim options As New List(Of String)(GetValues(obj, argument))
        Dim valueInList As Boolean = options.Contains(value)
        If valueInList Then
            Return Nothing
        Else
            Return "Value must be in the list of options."
        End If
    End Function

    Public Function ValidateTypeData(typeData As String) As Boolean Implements IArgHandler.ValidateTypeData
        Dim parts() As String = typeData.Split(":"c)
        If parts.Length = 0 Then
            Return False
        End If

        Dim command As String = parts(0)
        If Not mCommands.Contains(command) Then
            Dim argData As String = "object:" & typeData
            parts = argData.Split(":"c)
            command = parts(0)
            'Return False
        End If

        Select Case command
            Case "object"
                'returns a list of objects
                'format is "object:[objectType]:[objectSubType]"
                If parts.Count <> 3 Then
                    Return False
                End If

                Dim objectType As ObjectType = objectType.ALL
                If Not [Enum].TryParse(Of ObjectType)(parts(1), objectType) Then
                    Return False
                End If

                Dim subType As ObjectSubType = ObjectSubType.ALL
                If Not [Enum].TryParse(Of ObjectSubType)(parts(2), subType) Then
                    Return False
                End If
            Case "token"
                'returns a list of tokens within another object. ie "token:MOUNTAIN:POSITION:0" will return a list of argument 0 of all of the POSITION tokens.
                'optional 5th argument is a space delimited list of extra items to add
                'format is "token:objectId:tokenName:argIndex"

                If parts.Count < 4 OrElse parts.Count > 5 Then
                    Return False
                End If
            Case "file"
                'returns a list of strings taken from a file in the "Data" folder.
                'format is "file:filename"
            Case "material"
                'format is "material:objectId". replacements are valid
                If parts.Count <> 2 Then
                    Return False
                End If
            Case "tissue"
                'format is "tissue:objectId". replacements are valid
                If parts.Count <> 2 Then
                    Return False
                End If
        End Select

        Return True
    End Function

    Public ReadOnly Property TypeDataFormat As String Implements IArgHandler.TypeDataFormat
        Get
            Return "[object|token]:"
        End Get
    End Property

    Public ReadOnly Property DefaultTypeData As String Implements IArgHandler.DefaultTypeData
        Get
            Return ""
        End Get
    End Property

    Public ReadOnly Property DefaultValue(library As RawLibrary, obj As RawObject, argument As ArgumentDefinition) As String Implements IArgHandler.DefaultValue
        Get
            Dim ret As String = GetValues(obj, argument).FirstOrDefault
            If ret Is Nothing Then
                ret = ""
            End If
            Return ret
        End Get
    End Property

    Public Function Parse(argParts As System.Collections.Generic.IEnumerator(Of String), arg As ArgumentDefinition) As String Implements IArgHandler.Parse
        Return argParts.Current
    End Function

    Public Function GetNavList(library As RawLibrary, obj As RawObject, argument As ArgumentDefinition, value As String) As IEnumerable(Of NavPoint) Implements IArgHandler.GetNavList
        Dim ret As New List(Of NavPoint)
        Dim parts() As String = argument.TypeData.Split(":"c)
        If parts.Length = 2 AndAlso Not mCommands.Contains(parts(0)) Then
            Dim argData As String = "object:" & argument.TypeData
            parts = argData.Split(":"c)
        End If
        parts = ProcessReplacements(String.Join(":", parts), library, obj).Split(":"c)
        Dim command As String = parts(0)
        Select Case command
            Case "object"
                Dim objectType As ObjectType = objectType.ALL
                [Enum].TryParse(Of ObjectType)(parts(1), objectType)
                ret.Add(New NavPoint(value, value) With {.objectType = objectType})
            Case "token"
                Dim sourceObj As RawObject = library.GetObjectById(parts(1))
                Dim std As New List(Of String)
                For Each token As String In sourceObj.GetTokenArguments(parts(2), True)
                    Dim tokenParts() As String = token.Split(":"c)
                    std.Add(tokenParts(CInt(parts(3))))
                Next
                Dim realSourceObject As RawObject = sourceObj.GetTokenSourceObject(parts(2))

                If std.Contains(value) Then
                    If sourceObj IsNot Nothing Then
                        ret.Add(New NavPoint(parts(2) & ":" & value & " on " & realSourceObject.objectId, realSourceObject.objectId & ":" & parts(2) & ":" & parts(3) & ":" & value) With {.objectType = realSourceObject.ObjectType})
                    End If
                End If
            Case "material"
                ret.Add(New NavPoint(value, value))
            Case "tissue"
                ret.Add(New NavPoint(value, value))
        End Select

        Return ret
    End Function
End Class
